iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0

在第六天,我們已經討論了 直接安裝 vs 鎖定版本,也學會在 pyproject.toml[project].dependencies 裡,用 >===~= 等 PEP 440 語法來表達浮動或鎖定。

但這樣做只能控制 主依賴,間接依賴仍然可能隨意升級,導致「昨天能跑、今天就壞」的情況。

因此,在 Python 世界裡,關於「可重現環境」有三個進化階段:

  1. Constraints:用文字檔限制特定套件版本(傳統補救)。
  2. Hatch dependencies:在 pyproject.toml 中直接宣告依賴範圍(工程化表達)。
  3. UV lock:完整展開依賴樹,鎖檔保證一致性(現代最佳實務)。

Constraints:傳統的補救措施

在早期,pip 沒有 lock 檔的概念,所以社群用 constraints.txt 來補強。

  • requirements.txt → 主依賴清單(要安裝哪些套件)
  • constraints.txt → 補充規則(套件若被安裝,必須符合指定版本)

範例:

# constraints.txt
urllib3==2.2.3
charset-normalizer==3.3.2

安裝時帶入:

pip install -c constraints.txt requests

這樣即使 requests 間接依賴 urllib3,也會被鎖在 2.2.3

👉 適合:

  • 臨時修補間接依賴
  • 大公司提供「團隊統一白名單」

缺點:需要人工維護,而且只是「輔助規則」,不是完整鎖定。


Hatch dependencies:工程化的依賴表達

隨著 pyproject.toml 成為標準,我們可以直接在 [project].dependencies 裡,用 PEP 440 語法表達鎖定範圍:

[project]
name = "awesome-app"
dependencies = [
  "requests>=2.32,<3",     # 區間鎖定
  "python-dateutil==2.8.*", # 只允許 patch 升級
  "numpy~=1.26"            # 相容版號(允許 minor 升級)
]

這比 constraints.txt 更直觀,因為它 一開始就被納入專案設定,不用額外檔案。

再配合 [project.optional-dependencies],可同時管理開發工具與文件需求,並能跨 Hatch / pip / Poetry 重用。


UV lock:現代的可重現解法

雖然 pyproject.toml 已經能描述「想要的範圍」,但實際安裝時仍可能因間接依賴浮動而不一致。

這時候就需要 lock 檔

在 Python 世界,新世代的解法就是 UV

pyproject.toml

在這邊需要特別注意一下,我們將原本安裝套件使用的最原始的pip改成uv。

# 原始使用pip安裝依賴
#[tool.hatch.env.default] 
#requires = ["hatch-pip-compile"]

[tool.hatch.envs.default]
installer = "uv"

工作流

uv lock                # 產生 / 更新 uv.lock
uv sync --locked       # 嚴格依照 uv.lock 安裝
uv lock --upgrade      # 升級所有套件
uv lock --upgrade-package requests

執行 uv lock 指令會產生 uv.lock 檔案,裡面記錄了完整的依賴樹。往後在不同環境安裝時,只要透過 uv sync --locked,就能確保版本完全一致。

![https://ithelp.ithome.com.tw/upload/images/20250921/20178117QGuYCTki5a.png]


CI/CD:搭配 UV 的最小實例

在 GitHub Actions 中:

name: ci
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v3
      - name: Sync deps from lock
        run: uv sync --locked
      - name: Run tests
        run: uv run pytest -q

這樣能確保 CI 環境與本地完全一致,不會再有「在我電腦能跑」的窘境。


快取:加速與穩定

即使有了 lock,每次 CI 都從零安裝依賴還是很耗時。

可以快取 .hatch~/.cache/uv 來加速:

- uses: astral-sh/setup-uv@v3
  with:
    enable-cache: true

對照表:Constraints vs Hatch vs UV

項目 Constraints Hatch Dependencies UV Lock
控制範圍 部分套件(補充規則) 主依賴 + 範圍 全依賴樹
易用性 需額外檔案,人工維護 pyproject.toml 直接宣告 自動生成 uv.lock
重現性 部分一致 視間接依賴而定 完全一致
適用情境 臨時修補 / 白名單 專案需求表達 團隊工程化 / CI/CD

結論

就像前端專案使用 package-lock.jsonyarn.lock 來確保一致性,Python 世界現在也能透過 uv.lock 達到相同目標。

今天我們看到了可重現環境的三個層次:

  1. Constraints:傳統補救,只加限制,不會主動安裝。
  2. Hatch dependencies:PEP 440 標準化的依賴範圍表達。
  3. UV lock:現代最佳解,完整鎖定依賴樹,確保任何人、任何環境都能重現。

就像前端的 package-lock.json / yarn.lock,Python 世界現在也有了自己的解法。

constraints.txtpyproject.toml 的 dependencies,再到 uv.lock

我們正逐步走向一個更可靠、更工程化的 Python 生態 🚀。

明天我們將繼續探討 Day 8 - 一鍵化開發工作流:Hatch scripts × Nox


上一篇
Day 6 - 依賴管理策略:直接安裝 vs. 鎖定版本
下一篇
Day 8 - 一鍵化開發工作流:Hatch scripts × Nox
系列文
30 天 Python 專案工坊:環境、結構、測試到部署全打通8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言